/*
    Copyright 2006 Andrew Wilkinson <andrew@indiegigs.co.uk>

    This file is part of GL2x.

    GL2x is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    GL2x is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with GL2x; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <stdio.h>
#include <math.h>

#include "../driver.h"

void GL2x_renderTriangleOptimised1(GL2x_triangle_info* tri_info);
void GL2x_renderTriangleOptimised2(GL2x_triangle_info* tri_info);

//define interpolate(x1, y1, x2, y2, ty) (x1 + Fixed_round(Fixed_mul(Fixed_subi(Fixed_createi(x2), x1), Fixed_divi(Fixed_subi(ty, y1), (y2 - y1)))))
#define interpolate(z1, y1, z2, y2, ty) Fixed_add(z1, Fixed_mul(Fixed_sub(z2, z1), \
                                                                (Fixed_div(Fixed_sub(ty, y1), Fixed_sub(y2, y1)))))
#define interpolate_full(v1, y1, v2, y2, ty) Vector_add(v1, Vector_mul(Vector_sub(v2, v1), \
                                                                      (Fixed_div(Fixed_sub(ty, y1), Fixed_sub(y2, y1)))))

void GL2x_renderTriangle(GL2x_triangle_info* tri_info) {
    if(Fixed_eq(tri_info->v1.y, tri_info->v2.y) && Fixed_eq(tri_info->v1.y, tri_info->v3.y)) {
        return;
    }
    //FIXME: This is not how culling should be done.
    Fixed three_twenty = Fixed_createi(320);
    Fixed two_fourty = Fixed_createi(240);
    if(Fixed_lt(tri_info->v1.x, Fixed_zero) || Fixed_gt(tri_info->v1.x, three_twenty) ||
       Fixed_lt(tri_info->v2.x, Fixed_zero) || Fixed_gt(tri_info->v2.x, three_twenty) ||
       Fixed_lt(tri_info->v3.x, Fixed_zero) || Fixed_gt(tri_info->v3.x, three_twenty) ||
       Fixed_lt(tri_info->v1.y, Fixed_zero) || Fixed_gt(tri_info->v1.y, two_fourty) ||
       Fixed_lt(tri_info->v2.y, Fixed_zero) || Fixed_gt(tri_info->v2.y, two_fourty) ||
       Fixed_lt(tri_info->v3.y, Fixed_zero) || Fixed_gt(tri_info->v3.y, two_fourty)) {
        return;
    }

    if(Fixed_eq(tri_info->v1.y, tri_info->v2.y)) {
        if(Fixed_gt(tri_info->v3.y, tri_info->v1.y)) {
            GL2x_renderTriangleOptimised2(tri_info);
        } else {
            GL2x_renderTriangleOptimised1(tri_info);
        }
    } else if(Fixed_eq(tri_info->v1.y, tri_info->v3.y)) {
        if(Fixed_gt(tri_info->v2.y, tri_info->v1.y)) {
            GL2x_renderTriangleOptimised2(tri_info);
        } else {
            GL2x_renderTriangleOptimised1(tri_info);
        }
    } else if(Fixed_gt(tri_info->v1.y, tri_info->v2.y)) {
         if(Fixed_gt(tri_info->v3.y, tri_info->v1.y)) {
            Fixed midx = interpolate(tri_info->v3.x, tri_info->v3.y, tri_info->v2.x, tri_info->v2.y, tri_info->v1.y);
            Fixed midz = interpolate(tri_info->v3.z, tri_info->v3.y, tri_info->v2.z, tri_info->v2.y, tri_info->v1.y);
            GL2x_triangle_info tri2;

            tri2.v1 = tri_info->v3; tri2.v2 = tri_info->v1; makevector(tri2.v3, midx, tri_info->v3.y, midz, Fixed_one);
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c3; tri2.c2 = tri_info->c1;
                tri2.c3 = interpolate_full(tri_info->c3, tri_info->v3.y, tri_info->c2, tri_info->v2.y, tri_info->v1.y);
            }
            GL2x_renderTriangleOptimised2(&tri2);

            tri2.v1 = tri_info->v1; tri2.v2 = tri_info->v2;
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c1; tri2.c2 = tri_info->c2;
                //tri2.c3 = interpolate_full(tri_info->c3, tri_info->v3.y, tri_info->c2, tri_info->v2.y, tri_info->v1.y);
            }
            GL2x_renderTriangleOptimised1(&tri2);
        } else if(Fixed_lt(tri_info->v3.y, tri_info->v2.y)) {
            Fixed midx = interpolate(tri_info->v1.x, tri_info->v1.y, tri_info->v3.x, tri_info->v3.y, tri_info->v2.y);
            Fixed midz = interpolate(tri_info->v1.z, tri_info->v1.y, tri_info->v3.z, tri_info->v3.y, tri_info->v2.y);      // v1
            GL2x_triangle_info tri2;

            tri2.v1 = tri_info->v1; tri2.v2 = tri_info->v2; makevector(tri2.v3, midx, tri_info->v2.y, midz, Fixed_one);
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c1; tri2.c2 = tri_info->c2;
                tri2.c3 = interpolate_full(tri_info->c1, tri_info->v1.y, tri_info->c3, tri_info->v3.y, tri_info->v2.y);
            }
            GL2x_renderTriangleOptimised2(&tri2);  // v2

            tri2.v1 = tri_info->v2; tri2.v2 = tri_info->v3;
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c2; tri2.c2 = tri_info->c3;
                //tri2.c3 = interpolate_full(tri_info->c1, tri_info->v1.y, tri_info->c3, tri_info->v3.y, tri_info->v2.y);
            }
            GL2x_renderTriangleOptimised1(&tri2);  // v3
        } else {
            Fixed midx = interpolate(tri_info->v1.x, tri_info->v1.y, tri_info->v2.x, tri_info->v2.y, tri_info->v3.y);
            Fixed midz = interpolate(tri_info->v1.z, tri_info->v1.y, tri_info->v2.z, tri_info->v2.y, tri_info->v3.y);      // v1
            GL2x_triangle_info tri2;

            tri2.v1 = tri_info->v1; tri2.v2 = tri_info->v3; makevector(tri2.v3, midx, tri_info->v3.y, midz, Fixed_one);
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c1; tri2.c2 = tri_info->c3;
                tri2.c3 = interpolate_full(tri_info->c1, tri_info->v1.y, tri_info->c2, tri_info->v2.y, tri_info->v3.y);
            }
            GL2x_renderTriangleOptimised2(&tri2);  // v3

            tri2.v1 = tri_info->v3; tri2.v2 = tri_info->v2;
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c3; tri2.c2 = tri_info->c2;
                //tri2.c3 = interpolate_full(tri_info->c3, tri_info->v3.y, tri_info->c3, tri_info->v3.y, tri_info->v2.y);
            }
            GL2x_renderTriangleOptimised1(&tri2);  // v2
        }
    } else if(Fixed_lt(tri_info->v1.y, tri_info->v2.y)) {
        if(Fixed_gt(tri_info->v3.y, tri_info->v2.y)) {
            Fixed midx = interpolate(tri_info->v3.x, tri_info->v3.y, tri_info->v1.x, tri_info->v1.y, tri_info->v2.y);
            Fixed midz = interpolate(tri_info->v3.z, tri_info->v3.y, tri_info->v1.z, tri_info->v1.y, tri_info->v2.y);      // v3
            GL2x_triangle_info tri2;

            tri2.v1 = tri_info->v3; tri2.v2 = tri_info->v2; makevector(tri2.v3, midx, tri_info->v2.y, midz, Fixed_one);
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c3; tri2.c2 = tri_info->c2;
                tri2.c3 = interpolate_full(tri_info->c3, tri_info->v3.y, tri_info->c1, tri_info->v1.y, tri_info->v2.y);
            }
            GL2x_renderTriangleOptimised2(&tri2);  // v2

            tri2.v1 = tri_info->v2; tri2.v2 = tri_info->v1;
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c2; tri2.c2 = tri_info->c1;
                //tri2.c3 = interpolate_full(tri_info->c2, tri_info->v2.y, tri_info->c3, tri_info->v3.y, tri_info->v1.y);
            }
            GL2x_renderTriangleOptimised1(&tri2);  // v1
        } else if(Fixed_lt(tri_info->v3.y, tri_info->v1.y)) {
            Fixed midx = interpolate(tri_info->v2.x, tri_info->v2.y, tri_info->v3.x, tri_info->v3.y, tri_info->v1.y);
            Fixed midz = interpolate(tri_info->v2.z, tri_info->v2.y, tri_info->v3.z, tri_info->v3.y, tri_info->v1.y);      // v2

            GL2x_triangle_info tri2;
            tri2.v1 = tri_info->v2; tri2.v2 = tri_info->v1; makevector(tri2.v3, midx, tri_info->v1.y, midz, Fixed_one);
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c2; tri2.c2 = tri_info->c1;
                tri2.c3 = interpolate_full(tri_info->c2, tri_info->v2.y, tri_info->c3, tri_info->v3.y, tri_info->v1.y);
            }
            GL2x_renderTriangleOptimised2(&tri2);  // v1

            tri2.v1 = tri_info->v1; tri2.v2 = tri_info->v3;
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c1; tri2.c2 = tri_info->c3;
                //tri2.c3 = interpolate_full(tri_info->c2, tri_info->v2.y, tri_info->c3, tri_info->v3.y, tri_info->v1.y);
            }
            GL2x_renderTriangleOptimised1(&tri2);  // v3
        } else {
            Fixed midx = interpolate(tri_info->v2.x, tri_info->v2.y, tri_info->v1.x, tri_info->v1.y, tri_info->v3.y);
            Fixed midz = interpolate(tri_info->v2.z, tri_info->v2.y, tri_info->v1.z, tri_info->v1.y, tri_info->v3.y);      // v2

            GL2x_triangle_info tri2;
            tri2.v1 = tri_info->v2; tri2.v2 = tri_info->v3; makevector(tri2.v3, midx, tri_info->v3.y, midz, Fixed_one);
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c2; tri2.c2 = tri_info->c3;
                tri2.c3 = interpolate_full(tri_info->c2, tri_info->v2.y, tri_info->c1, tri_info->v1.y, tri_info->v3.y);
            }
            GL2x_renderTriangleOptimised2(&tri2);  // v3

            tri2.v1 = tri_info->v3; tri2.v2 = tri_info->v1;
            if(tri_flags.colour) {
                tri2.c1 = tri_info->c3; tri2.c2 = tri_info->c1;
                //tri2.c3 = interpolate_full(tri_info->c3, tri_info->v3.y, tri_info->c2, tri_info->v2.y, tri_info->v1.y);
            }
            GL2x_renderTriangleOptimised1(&tri2);  // v1
        }
    } else {
        fprintf(stderr, "Error, wierd triangle!\n");
    }
}

/* v3.y == v1.y
 * Flat top, v2 is the bottom point
 */
void GL2x_renderTriangleOptimised1(GL2x_triangle_info* tri_info) {
    int x2i;
    int v1y;
    Fixed* zptr;

    GL2x_triangle_pos tp;
    GL2x_triangle_delta td;

    if(Fixed_round(tri_info->v1.x) == Fixed_round(tri_info->v3.x) || Fixed_round(tri_info->v1.y) == Fixed_round(tri_info->v2.y)) {
        return;
    } else if(Fixed_lt(tri_info->v1.x, tri_info->v3.x)) {
        td.dy = Fixed_round(tri_info->v1.y) - Fixed_round(tri_info->v2.y);

        td.d1x = Fixed_divi(Fixed_sub(tri_info->v1.x, tri_info->v2.x), td.dy);
        td.d2x = Fixed_divi(Fixed_sub(tri_info->v3.x, tri_info->v2.x), td.dy);

        td.d1z = Fixed_divi(Fixed_sub(tri_info->v1.z, tri_info->v2.z), td.dy);
        td.d2z = Fixed_divi(Fixed_sub(tri_info->v3.z, tri_info->v2.z), td.dy);

        if(tri_flags.colour) {
            td.d1c = Vector_divi(Vector_sub(tri_info->c1, tri_info->c2), td.dy);
            td.d2c = Vector_divi(Vector_sub(tri_info->c3, tri_info->c2), td.dy);
        }
    } else {
        td.dy = Fixed_round(tri_info->v1.y) - Fixed_round(tri_info->v2.y);

        td.d1x = Fixed_divi(Fixed_sub(tri_info->v3.x, tri_info->v2.x), td.dy);
        td.d2x = Fixed_divi(Fixed_sub(tri_info->v1.x, tri_info->v2.x), td.dy);

        td.d1z = Fixed_divi(Fixed_sub(tri_info->v3.z, tri_info->v2.z), td.dy);
        td.d2z = Fixed_divi(Fixed_sub(tri_info->v1.z, tri_info->v2.z), td.dy);

        if(tri_flags.colour) {
            td.d1c = Vector_divi(Vector_sub(tri_info->c3, tri_info->c2), td.dy);
            td.d2c = Vector_divi(Vector_sub(tri_info->c1, tri_info->c2), td.dy);
        }
    }

    td.x_start = td.x_end = tri_info->v2.x;
    td.z_start = td.z_end = tri_info->v2.z;
    if(tri_flags.colour) {
        td.c_start = td.c_end = tri_info->c2;
    }

    tp.y = Fixed_round(tri_info->v2.y);
    v1y = Fixed_round(tri_info->v1.y);
    while(tp.y < v1y) {
        tp.z = td.z_start;
        td.dz = Fixed_div(Fixed_sub(td.z_end, td.z_start), Fixed_sub(td.x_end, td.x_start));
        if(tri_flags.colour) {
            tp.colour = td.c_start;
            td.dc = Vector_div(Vector_sub(td.c_end, td.c_start), Fixed_sub(td.x_end, td.x_start));
        }

        zptr = &(GL2x_z_buffer[tp.y*320 + Fixed_round(td.x_start)]);
        x2i = Fixed_round(td.x_end);
        for(tp.x=Fixed_round(td.x_start); tp.x < x2i; tp.x++) {
            if(Fixed_ge(*zptr, tp.z)) {
                *zptr = tp.z;
                current_shader(&tp);
            }
            tp.z = Fixed_add(tp.z, td.dz); zptr++;
            if(tri_flags.colour) {
                tp.colour = Vector_add(tp.colour, td.dc);
            }
        }

        tp.y++;
        td.x_start = Fixed_add(td.x_start, td.d1x); td.x_end = Fixed_add(td.x_end, td.d2x);
        td.z_start = Fixed_add(td.z_start, td.d1z); td.z_end = Fixed_add(td.z_end, td.d2z);
        if(tri_flags.colour) {
            td.c_start = Vector_add(td.c_start, td.d1c); td.c_end = Vector_add(td.c_end, td.d2c);
        }
    }
}

/* v3.y == v2.y
 * Flat bottom, v1 is the top point
 */
void GL2x_renderTriangleOptimised2(GL2x_triangle_info* tri_info) {
    int x2i;
    int v2y;
    Fixed* zptr;

    GL2x_triangle_pos tp;
    GL2x_triangle_delta td;

    if(Fixed_round(tri_info->v2.x) == Fixed_round(tri_info->v3.x) || Fixed_round(tri_info->v1.y) == Fixed_round(tri_info->v2.y)) {
        return;
    } else if(Fixed_lt(tri_info->v2.x, tri_info->v3.x)) {
        td.dy = Fixed_round(tri_info->v2.y) - Fixed_round(tri_info->v1.y);

        td.d1x = Fixed_divi(Fixed_sub(tri_info->v1.x, tri_info->v2.x), td.dy);
        td.d2x = Fixed_divi(Fixed_sub(tri_info->v1.x, tri_info->v3.x), td.dy);
        td.x_start = td.x_end = tri_info->v1.x;

        td.d1z = Fixed_divi(Fixed_sub(tri_info->v1.z, tri_info->v2.z), td.dy);
        td.d2z = Fixed_divi(Fixed_sub(tri_info->v1.z, tri_info->v3.z), td.dy);
        td.z_start = td.z_end = tri_info->v1.z;

        if(tri_flags.colour) {
            td.d1c = Vector_divi(Vector_sub(tri_info->c1, tri_info->c2), td.dy);
            td.d2c = Vector_divi(Vector_sub(tri_info->c1, tri_info->c3), td.dy);
            td.c_start = td.c_end = tri_info->c1;
        }
    } else {
        td.dy = Fixed_round(tri_info->v2.y) - Fixed_round(tri_info->v1.y);

        td.d1x = Fixed_divi(Fixed_sub(tri_info->v1.x, tri_info->v3.x), td.dy);
        td.d2x = Fixed_divi(Fixed_sub(tri_info->v1.x, tri_info->v2.x), td.dy);
        td.x_start = td.x_end = tri_info->v1.x;

        td.d1z = Fixed_divi(Fixed_sub(tri_info->v1.z, tri_info->v3.z), td.dy);
        td.d2z = Fixed_divi(Fixed_sub(tri_info->v1.z, tri_info->v2.z), td.dy);
        td.z_start = td.z_end = tri_info->v1.z;

        if(tri_flags.colour) {
            td.d1c = Vector_divi(Vector_sub(tri_info->c1, tri_info->c3), td.dy);
            td.d2c = Vector_divi(Vector_sub(tri_info->c1, tri_info->c2), td.dy);
            td.c_start = td.c_end = tri_info->c1;
        }
    }

    tp.y = Fixed_round(tri_info->v1.y);
    v2y = Fixed_round(tri_info->v2.y);
    while(tp.y >= v2y) {
        tp.z = td.z_start;
        td.dz = Fixed_div(Fixed_sub(td.z_end, td.z_start), Fixed_sub(td.x_end, td.x_start));

        if(tri_flags.colour) {
            tp.colour = td.c_start;
            td.dc = Vector_div(Vector_sub(td.c_end, td.c_start), Fixed_sub(td.x_end, td.x_start));
        }

        zptr = &(GL2x_z_buffer[tp.y*320 + Fixed_round(td.x_start)]);
        x2i = Fixed_round(td.x_end);
        for(tp.x=Fixed_round(td.x_start); tp.x < x2i; tp.x++) {
            if(Fixed_ge(*zptr, tp.z)) {
                *zptr = tp.z;
                current_shader(&tp);
            }
            tp.z = Fixed_add(tp.z, td.dz); zptr++;
            if(tri_flags.colour) {
                tp.colour = Vector_add(tp.colour, td.dc);
            }
        }

        tp.y--;
        td.x_start = Fixed_add(td.x_start, td.d1x); td.x_end = Fixed_add(td.x_end, td.d2x);
        td.z_start = Fixed_add(td.z_start, td.d1z); td.z_end = Fixed_add(td.z_end, td.d2z);
        if(tri_flags.colour) {
            td.c_start = Vector_add(td.c_start, td.d1c); td.c_end = Vector_add(td.c_end, td.d2c);
        }
    }
}
